##########################################
##GUI for vdart2. App:no use
##  wFn:the weighting function for BPS search algorithm
##  loadDset: helper function to convert dataset csv into dataset dictionary, for search initialization
##  constraints: adds initial step to policy search that allows user to set specifications (ie: sex must
##        be separate) to hold true in all policies
##  executeranges: contraints() helper function
##  fieldEval: gives rating of each policy field's specifications, approx a 1-10 scale
##  Tri class: displays policies to user
##########################################


from Tkinter import *
import tkMessageBox
from math import pi,sin,sqrt,cos,log
class App:
	"""disregard"""

	def __init__(self, master):
		self.master=master
        	self.create_menu(master)


        	"""self.label=Label(self.frame,text="Would you like to?")
		self.label.pack(side=TOP)
		self.b=Button(self.frame,text="Yes",command=self.ok)
		self.b.pack(side=TOP)
		self.b2=Button(self.frame,text="No",command=self.exit)
		self.b2.pack(side=TOP)"""

	def callback(self):
		print "hello friend"
	
	def exit(self):
		self.master.withdraw
	
	def exitSettings(self):
		self.top.withdraw

	def create_menu(self, master):
		men=Menu(master)
		master.config(menu=men)
		filemenu=Menu(men)
		men.add_cascade(label="File",menu=filemenu)
		filemenu.add_command(label="New",command=self.callback)
		filemenu.add_command(label="Foo",command=self.callback)
		filemenu.add_command(label="Exit",command=self.exit)
		filemenu.add_command(label="Settings",
 command=self.create_settings)

	def create_settings(self):
		self.top=Toplevel(self.master)
		self.top.title("Settings")
		msg=Message(self.top,text="Please choose the correct configuration for the location of the database where necessary census data will be loaded").grid(rowspan=3,columnspan=2,pady=10)
		
		Label(self.top,text="Engine:").grid(row=3,sticky=E,pady=10)
		Label(self.top,text="Username:").grid(row=4,sticky=E,pady=10)
		Label(self.top,text="Password:").grid(row=5,sticky=E,pady=10)
		Label(self.top,text="Host:").grid(row=6,sticky=E,pady=10)
		Label(self.top,text="Port#:").grid(row=7,sticky=E,pady=10)
		Label(self.top,text="Database name:").grid(row=8,sticky=E,pady=10)
		Label(self.top,text="PathtoZipFiles:").grid(row=9,sticky=E,pady=10)
		Label(self.top,text="PathtoCountyFiles:").grid(row=10,sticky=E,pady=10)
		
		e0=Entry(self.top).grid(row=3,column=1)
		e1=Entry(self.top).grid(row=4,column=1)
		e2=Entry(self.top).grid(row=5,column=1)
		e3=Entry(self.top).grid(row=6,column=1)
		e4=Entry(self.top).grid(row=7,column=1)
		e5=Entry(self.top).grid(row=8,column=1)
		e6=Entry(self.top).grid(row=9,column=1)
		e7=Entry(self.top).grid(row=10,column=1)

		button=Button(self.top,text="Ok",command=self.insertSettings).grid(row=11,column=0,sticky=E)
		button2=Button(self.top,text="Cancel",command=self.exitSettings).grid(row=11,column=1,sticky=W)


	def insertSettings(self):
		print "you did it!"


def wFn(solPols,polSize,vocab,fields):
	"""weighting function for halfwaypoint calc"""
	from math import log
#the more weighted, the LESS likely the bit will become a 1
	bitweights=[1]*polSize
 	#create field ranges
	franges={}
	range1=0
#scale will cause large bins to more likely be 0
	scale={}
	for fieldIndex in fields:
		range2=range1+len([keys for keys in vocab if keys[0]==fieldIndex])-2
		franges[fields[fieldIndex]]=[range1,range2]
		scale[fields[fieldIndex]]=log(range2-range1+2,5)
		range1=range2+1			
	#weight appropriate to size of category
	for field in franges:
		startbit=franges[field][0]
		while startbit<=franges[field][1]:
			bitweights[startbit]*=scale[field]
			startbit+=1
	for policy in solPols:
		for a in range(polSize):
			if policy[a]==1:
				for field in franges:
					if franges[field][0]<=a<=franges[field][1]:
						myscale=scale[field]
				bitweights[a]+=myscale
	return bitweights

		
def loadDset(pathToFile):
	"""helper function to convert dataset csv into dataset dictionary, for search initialization"""
	import csv
	file=open(pathToFile)
	reader=csv.reader(file,delimiter=',')
	dset=[]
	for line in reader:
		toApend={'age':line[2],'race':line[3],'zip5':line[1],'sex':line[0]}
		dset.append(toApend)
	file.close()
	return dset


def constraints(vocab,fields,vocablist,polSize):
	"""adds initial step to policy search that allows user to set \
specifications (ie: sex must be separate) to hold true in all policies"""
	import policyEvaluation
	genPolicy=policyEvaluation.PolicyString.mostGeneral(polSize)
	constraints=raw_input('Do you have any constraints?(Y/N) ')
	constraints=constraints.upper()
	if constraints=='Y':
		print('\n')*2
		F=raw_input('What field(Age,Sex,ZipCode,Race)? ')
		F=F.upper()
		#if user input string recognized as a field, turn bits of the corresponding ranges to 1
		if 'AGE' in F:
			print('\n')*2
			print('What age ranges must stay seperate? \'3-6\' means 3,4,5,6 will not be grouped')
			userin=raw_input('You can specify multiple ranges with \',\' (ex: 3-5,10-12,20-30): ')
			genPolicy=executeRanges(userin.split(','),'age',vocab,fields,vocablist,genPolicy)

		if 'SEX' in F:
			print('\n')*2
			userin=raw_input('Do you want sex (m/f) to stay specified?(Y/N) ')
			if userin.upper()=='Y':
				startIndex=0
				count=0
				ph=policyEvaluation.PolicyHandler(vocab,fields)	
				fieldindex=ph.fieldLookup['sex']
				while count!=fieldindex:
					startIndex+=len([i for i in vocab.keys() if i[0] == count])-1
					count+=1
				genPolicy[startIndex]=1

		if 'RACE' in F:
			print('\n')*2
			ph=policyEvaluation.PolicyHandler(vocab,fields)	
			fieldindex=ph.fieldLookup['race']
			print('What races must remain seperate?')
			print(vocablist[fieldindex])
			userin=raw_input('Specify range(s) (eg: \'0-2,3-4\') ')
			genPolicy=executeRanges(userin.split(','),'race',vocab,fields,vocablist,genPolicy)

		if 'ZIPCODE' in F:
			print('\n')*2
			userin=raw_input('What zipcodes must remain seperate? (eg: 31232-37235,30152-30064) ')
			genPolicy=executeRanges(userin.split(','),'zip5',vocab,fields,vocablist,genPolicy)
			


			
	return genPolicy	
		



def executeRanges(ranges,fieldname,vocab,fields,vocablist,genPolicy):
	"""helper function for constraints()"""
	import policyEvaluation
	#takes ranges, [3-5,10-12,18-20] etc and splits into integers to work with
	for r in ranges:
			values=r.split('-')
			minval=int(values[0])
			maxval=int(values[1])
			ph=policyEvaluation.PolicyHandler(vocab,fields)	
			fieldindex=ph.fieldLookup[fieldname]
			startIndex=0
			count=0
			while count!=fieldindex:
				startIndex+=len([i for i in vocab.keys() if i[0] == count])-1
				count+=1
			count=0
			if fieldname=='race':
				count=minval

			else:	
				while str(vocablist[fieldindex][count])!=str(minval):
					count+=1
			startIndex+=count
			for a in range(maxval-minval):
				genPolicy[startIndex]=True # separates all values in original range
				startIndex+=1
	return genPolicy



def fieldEval(policy,vocab,fields):
	from math import log,sqrt
	import numpy
	fieldCounts=[]
	currentIndex=0
	for fieldIndex in fields:
		#use a fairly normalized scale		
		total=len([keys for keys in vocab if keys[0]==fieldIndex])-1
		bitCount=0
		binNum=1
		binList=[]
		for index in range(total):
			if policy[currentIndex]:
				bitCount+=1
				binList.append(binNum)
				binNum=0
			currentIndex+=1
			binNum+=1  #will get the size of all bins, only used for zipcode
			#method for score calculation can be tweaked 
		if fields[fieldIndex]=='race':
			score=float(bitCount)/float(total)*10
		if fields[fieldIndex]=='sex':
			score=float(bitCount)/float(total)*10
		if fields[fieldIndex]=='age':
			score=float(bitCount)/float(total)*50
		if fields[fieldIndex]=='zip5':
			#score=float(bitCount)/float(total)
			#score=(log(score,2)+5)*2  #experiment with different log bases
			score=1./(numpy.average(numpy.array(binList))+numpy.std(numpy.array(binList)))*100  #takes size and spread of bin as rating

		fieldCounts.append(score*score)
	fieldCounts.append(sum(fieldCounts))
	return fieldCounts

class Tri:
	"""Class for displaying policies to user and allowing real time search
	Controls: mouse button rotates model, arrow keys shifts up/down/left/right, 
	enter to zoom in, shift enter to zoom out (NOTE: zoom still very buggy)"""

	def __init__(self, master,solutionPolicies,vocab,fields):
		"""Creates triangular interface for policy display, change OFFSET to change top left corner"""
		OFFSET=[160,180]
		SIDELENGTH=600
		HEIGHT=SIDELENGTH*sin(60./180*pi)
		self.vocab=vocab
		self.fields=fields
		self.solPols=solutionPolicies
		self.pointList=[]  # keep track of point objects for scatter, before transforming
		#creates coordinates of inverted triangle
		Coords=sum([OFFSET,[OFFSET[0]+SIDELENGTH],[OFFSET[1]],[OFFSET[0]+SIDELENGTH/2],[OFFSET[1]+HEIGHT]],[])
		self.Coords=Coords
		#finds center of canvas, kind of. can be changed if need be
		self.centroid=[float(Coords[0]+Coords[2]+Coords[4])/3,float(Coords[1]+Coords[3]+Coords[5])/3]
		#scaleDist=max distance that can be traveled down a field axis
		self.scaleDist=self.distForm(self.centroid,Coords[0:2])
		#creates coordinates of right side up triange and combines with Coords to form hexagon
		self.backgroundCoords=self.create_BCoords(Coords)
		self.master=master #Tk()
		self.canvas=Canvas(self.master,height=700,width=1000,background='goldenrod1') #where everything is displayed
		self.canvas.grid(sticky=N+W)
		#creates vandy logo at top left of screen
		photo=PhotoImage(file='/Users/Alec_/Desktop/vanderbilt_logo.gif')
		photo=photo.subsample(2,2)
		imageRef=self.canvas.create_image(100,100,image=photo)
		self.create_Legend()  #creates legend at bottom left
		self.popUpWindow1=None 
		self.popUpWindow2=None
		self.TT=None  ##zipcode pop up window text
		#self.canvas.create_polygon(self.backgroundCoords,fill='goldenrod1',outline='black') #3
		#self.format() #initializes background lines, etc #3
		#plots the policies
		#self.plotRelative(vocab,fields) #my 2d alternative way of plotting #3
		#########this is the core code##########
		self.projAttempt4() #creates rotatable axes and points, contains camera settings
		#######################################
		#for a in range(0,6,2):#3
			#self.canvas.create_line(self.centroid,self.Coords[a:a+2])#3
		#self.canvas.bind("<Button-1>",self.eventT) #uncomment to quickly click for canvas coords at mouse
		self.xyMouse=[]
		self.xyMouse=self.canvas.bind("<Button-1>",self.initMouse) #intitialize mouse position
		self.canvas.bind("<B1-Motion>",self.rotate)  #rotates camera
		self.canvas.bind("<Shift-B1-Motion>",self.rotateFine) #fine tunes camera rotation
		self.canvas.bind("<Return>",self.incDistance)
		self.canvas.bind("<Shift-Return>",self.decDistance)
		self.canvas.bind("<KeyPress>",self.moveModel) # allows user to shift model up/down/left/right
		
		self.canvas.focus_set()  #gives canvas focus for moveModel bind
		master.mainloop()##
	####################################################	
	
	def create_BCoords(self,coords):
		"""creates coordinates for background """
		#initialize rightside up triangle coords
		c2=[self.centroid[0],self.centroid[1]-self.scaleDist,self.centroid[0]+self.scaleDist*cos(30./180*pi),self.centroid[1]+self.scaleDist*sin(30./180*pi),self.centroid[0]-self.scaleDist*cos(30./180*pi),self.centroid[1]+self.scaleDist*sin(30./180*pi)]
		#combine the coordinates to create hexagon
		newcoords=[]
		for a in range(0,6,2):
			newcoords.append(coords[a])
			newcoords.append(coords[a+1])
			newcoords.append(c2[a])
			newcoords.append(c2[a+1])
		return newcoords



	def format(self):
		"""Creates background of display, ie: text, lines"""
		#note: make customizable with OFFSET
		self.canvas.create_text(153,38+120,text='Race',font=('Helvetica',16))
		self.canvas.create_text(777, 41+120,text='Age',font=('Helvetica',16))
		self.canvas.create_text(554,684,text='ZipCode',font=('Helvetica',16))	
		for a in range(2,12,4):
			self.canvas.create_line(self.centroid[0],self.centroid[1],self.backgroundCoords[a:a+2],width=1,dash=4)
		self.canvas.create_text(788,570,text='Gender Given:',font=('Helvetica',16))	
		self.canvas.create_text(788,600,text='Gender Hidden:',font=('Helvetica',16))
		self.canvas.create_oval(858+3,571+3,858-3,571-3,fill='black',width=1) #for legend
		self.canvas.create_polygon(859,596+2*4,859-1*4,596,859+1*4,596,fill='black') #for legend
	
	def plotRelative(self,vocab,fields):
		"""Plots the policies in the solution space, more details to come"""
		#the higher the scale, the less it affect position
		RACESCALE=10 #10
		AGESCALE=20
		ZIPSCALE=15  #15
		DOTRADIUS=8
		TRISIZE=4
		assert fields[0].upper()=='RACE'
		assert fields[2].upper()=='AGE'
		assert fields[3].upper()=='ZIP5'
		for policy in self.solPols:
			ratings=fieldEval(policy,vocab,fields)
			#note: make more compatible, with different ordered fields
			raceDist=float(ratings[0])/RACESCALE*self.scaleDist
			if raceDist>self.scaleDist:
				raceDist=self.scaleDist-3
			ageDist=float(ratings[2])/AGESCALE*self.scaleDist
			if ageDist>self.scaleDist:
				ageDist=self.scaleDist-3
			zipDist=float(ratings[3])/ZIPSCALE*self.scaleDist	
			if zipDist>self.scaleDist:
				zipDist=self.scaleDist-3
			#shift policy coordinates down proportionally to zipcode rating
			C=[self.centroid[0],self.centroid[1]+zipDist] 
			#now shift coordinates of policy by race,age ratings
			C[0]-=raceDist*cos(30./180*pi)
			C[1]-=raceDist*sin(30./180*pi)
			C[0]+=ageDist*cos(30./180*pi)
			C[1]-=ageDist*sin(30./180*pi)
			if ratings[1]==10:
				self.pointList.append(self.canvas.create_oval(C[0]+DOTRADIUS,C[1]+DOTRADIUS,C[0]-DOTRADIUS,C[1]-DOTRADIUS,fill='black',width=1,activefill='white',activeoutline='white',outline='black'))
			if ratings[1]==0:
				self.pointList.append(self.canvas.create_polygon(C[0],C[1]+2*TRISIZE,C[0]-1*TRISIZE,C[1],C[0]+1*TRISIZE,C[1],fill='black',activefill='white',activeoutline='white'))
		
		
	def plot3D(self,vocab,fields):
		"""similar to plot relative, but plots absolute value, (very rudimentary)
		of ratings race,age,zip on axes x,y,z  """
		RACESCALE=10 #10
		AGESCALE=20
		ZIPSCALE=15  #15
		DOTRADIUS=1
		MAXDOTSIZE=8  #the actuall maxsize=maxdotsize+dotradius
		TRISIZE=4
		assert fields[0].upper()=='RACE'
		assert fields[2].upper()=='AGE'
		assert fields[3].upper()=='ZIP5'
		for policy in self.solPols:
			ratings=fieldEval(policy,vocab,fields)
			#note: make more compatible, with different ordered fields
			raceDist=float(ratings[0])/RACESCALE*self.scaleDist
			if raceDist>self.scaleDist:
				raceDist=self.scaleDist-3
			ageDist=float(ratings[2])/AGESCALE*self.scaleDist
			if ageDist>self.scaleDist:
				ageDist=self.scaleDist-3
			zipDist=float(ratings[3])/ZIPSCALE*self.scaleDist	
			if zipDist>self.scaleDist:
				zipDist=self.scaleDist+3
			C=[self.centroid[0]+10,self.centroid[1]] #note plus 10
			#determine location of point
			C[0]=C[0]+ageDist*cos(30./180*pi)-raceDist*cos(30./180*pi)
			C[1]=C[1]+ageDist*sin(30./180*pi)+raceDist*sin(30./180*pi)
			C[1]=C[1]-zipDist
			proportionxy=(ageDist+raceDist)/2 #determine size/depth of dots
			proportionxy/=self.scaleDist
			R=DOTRADIUS+MAXDOTSIZE*proportionxy
			self.canvas.create_oval(C[0]+R,C[1]+R,C[0]-R,C[1]-R,fill='black',width=1,activefill='white',activeoutline='white',outline='white')
			
	
	def plot3Dmatlab(self,vocab,fields):
		from numpy import array
		import matplotlib.pyplot as plt
		from mpl_toolkits.mplot3d import Axes3D
		RACESCALE=10 #10
		AGESCALE=20
		ZIPSCALE=100  #15
		assert fields[0].upper()=='RACE'
		assert fields[2].upper()=='AGE'
		assert fields[3].upper()=='ZIP5'
		xPts=[]
		yPts=[]
		zPts=[]
		for policy in self.solPols:
			ratings=fieldEval(policy,vocab,fields)
			#note: make more compatible, with different ordered fields
			raceDist=float(ratings[0])/RACESCALE*self.scaleDist
			if raceDist>self.scaleDist:
				raceDist=self.scaleDist-3
			ageDist=float(ratings[2])/AGESCALE*self.scaleDist
			if ageDist>self.scaleDist:
				ageDist=self.scaleDist-3
			zipDist=float(ratings[3])/ZIPSCALE*self.scaleDist	
			if zipDist>self.scaleDist:
				zipDist=self.scaleDist+3
			xPts.append(raceDist)
			yPts.append(ageDist)
			zPts.append(zipDist)
		xArray=array(xPts)
		yArray=array(yPts)
		zArray=array(zPts)
		fig=plt.figure()
		fig.subplots_adjust(bottom=.2)
		ax=fig.add_subplot(111,projection='3d')
		ax.scatter(xArray,yArray,zArray)
		plt.show()


	def eventT(self,event):
		"""To test point responsiveness
		for point in self.pointList:
			coords=self.canvas.coords(point)
			self.canvas.coords(point,coords[0]+10,coords[1]+10,coords[2]+10,coords[3]+10)"""
		print(event.x,event.y)



	def create_Legend(self):
		graphCenter=[60,640]
		self.canvas.create_line(graphCenter,graphCenter[0],graphCenter[1]-30,dash=4)	
		self.canvas.create_line(graphCenter,graphCenter[0]-30,graphCenter[1]+20)
		self.canvas.create_line(graphCenter,graphCenter[0]+30,graphCenter[1]+20,width=2)
		self.canvas.create_text(graphCenter[0],graphCenter[1]+35,text='Proper Orientation',font=('Helvetica',10))
		legendCenter=[110,630]
		self.canvas.create_text(legendCenter,text='Gender Specified:',font=('Helvetica',10),anchor='w')
		self.canvas.create_text(legendCenter[0],legendCenter[1]+30,text='Gender Generalized:',font=('Helvetica',10),anchor='w')
		keyOval1=[200,630]
		keyOval2=[210,660]
		self.canvas.create_oval(keyOval1[0]+4,keyOval1[1]+4,keyOval1[0]-4,keyOval1[1]-4,fill='black')
		self.canvas.create_oval(keyOval2[0]+4,keyOval2[1]+4,keyOval2[0]-4,keyOval2[1]-4,fill='white',outline='black')
		b=Button(self.canvas,text='Default',command=self.restoreDefault,highlightbackground='goldenrod1')
		b.place(x=self.centroid[0]-50,y=self.centroid[1]+300)

		
		



	def restoreDefault(self):
		#reorients graph back to factory settings
		self.elevation=30./180*pi 
		self.rotation=135./180*pi   
		self.distance=10
		self.updateTasks()


	def displayPt(self,event,ptString):	
		"""displays the features of the point clicked, ie: policy string"""
		self.updateOverlap(debug=True)
		print(self.scatDepth[self.ptObjs.index(int(ptString))])
		#NOTE:no compatibility with 'x-ing out' of pop up windows
		#ptString is identifier for point in self.ptObjs, --which point was clicked--
		#self.prevPt is a list of [object id,defaultFill,defaultOutline] from last call to this function
		if not self.prevPt==[]: 
			#if self.prevPt==[], then this is the first pt clicked, or after close button
			self.closeWindow(self.prevPt[0],self.prevPt[1],self.prevPt[2],closePops=False)
		if not self.popUpWindow1==None:
			self.printPolString(ptString)  #should stay up if another point clicked
		if not self.popUpWindow2==None:  #if popupwindow is already up when point clicked, then update popup
			self.pprintPol(ptString)	
		if not self.TT==None:   #zipwindow is already up
			self.createZipWd(self.policyLookup[int(ptString)],update=True)
		configDict=self.canvas.itemconfig(ptString) #to access default colors
		defaultFill=configDict['fill'][4]
		defaultOutline=configDict['outline'][4]
		self.canvas.itemconfig(ptString,fill='red',outline='black') #to simulate staying active until window close
		ptDisplay=Frame(height=160,width=160,background='white',relief='solid',borderwidth=3) #used when point is clicked
		######what the window will say#########
		rating=self.ratingLookup[int(ptString)]
		text1='The Policy you picked has a rating of: '+'%.2f' % rating
		L1=Label(ptDisplay,text=text1)
		L2=Label(ptDisplay,text='Print Policy:')
		L1.grid(sticky='n',columnspan=2)
		L2.grid(row=1,column=0,columnspan=2)
		#######################################
		B1=Button(ptDisplay,text='Close',command=lambda:self.closeWindow(ptString,defaultFill,defaultOutline)) #creates close btn
		B2=Button(ptDisplay,text='Bits',command=lambda:self.printPolString(ptString)) #will print policy string
		B3=Button(ptDisplay,text='Specs',command=lambda:self.pprintPol(ptString)) #will pretty print policy
		B1.grid(sticky='s',row=3,column=0,pady=5,columnspan=2)
		B2.grid(sticky='e',row=2,column=0,pady=5,padx=5)
		B3.grid(sticky='w',row=2,column=1,pady=5,padx=5)
		window=self.canvas.create_window(825,140,window=ptDisplay,tags='window1',anchor='n')
		self.prevPt=[ptString,defaultFill,defaultOutline] #so color changes back in case another point clicked before close button 



	def updateOverlap(self,debug=False):
		#makes sure all points are overlapping correctly, corresponding to depth
		for ptString in self.ptObjs:
			orderedPts=[]
			#ptString=str(ptString)
			bbox=self.canvas.bbox(ptString)  #gives bounding box of point
			olapPts=self.canvas.find_overlapping(bbox[0],bbox[1],bbox[2],bbox[3])  #gives points the first one overlaps
			if debug:
				import pdb
				pdb.set_trace()
			if len(olapPts) > 1:   #if 1, means only point bbox was called on is there, skip
				olapPts=list(olapPts)   #so its mutable
				#remove overlapping points that arent scatter pts
				badPoints=[]
				for point in olapPts:
					if not (point in self.ptObjs):
						badPoints.append(point) #can't just remove, bad things happen with iter
				for point in badPoints:
					olapPts.remove(point)
				#continue
				orderedPts=[olapPts[0]]
				for pt in olapPts[1:]:    #this loop orders olapPts based on scapdepth, into orderedPts
				 if pt in self.ptObjs:
					Index=0
					loop=True
					while(loop):
						mydepth=self.scatDepth[self.ptObjs.index(int(pt))]  #depth of corresponding pt object  
						if Index==len(orderedPts):   #got to end, put pt at back of orderedPts
							orderedPts.append(pt)
							break   
					 	if mydepth<self.scatDepth[self.ptObjs.index(orderedPts[Index])]: 
							orderedPts.insert(Index,pt)
							loop=False
						Index+=1
				#now raise points baised on depth
				
				for pt in orderedPts:
					self.canvas.tag_raise(str(pt))   #should raise in order of less depth to more depth
						

			

		
	def printPolString(self,pointObject):
		from policyEvaluation import PolicyHandler
		pH=PolicyHandler(self.vocab,self.fields)
		zipIndex=pH.fieldLookup['zip5']
		#opens new window, prints bits of policy array
		policy=self.policyLookup[int(pointObject)]
		#uses toplevel initiated in __init__	
		index=len(self.vocab)-len(self.fields)-len(pH.vocabLists[zipIndex])+1  #this is the index of the start of zip5
		text='Your policy bitarray is: \n \n Race, sex, and age: \n \n'+str(policy)[0:index]+'\n \n Zipcodes: \n \n'+str(policy)[index:]
		#note: change racesexage string to more compatible
		if self.popUpWindow1==None:  #only make once
			self.popUpWindow1=Toplevel(self.master,bd=3,relief='flat') #will be pop up window for displayPt method's printPolicy bind
			self.popUpWindow1.geometry('400x300+800-400') #see geometry strings in tkinter tutorial
			self.popUpWindow1.transient(self.canvas) #will always be on top
			self.v1=StringVar()
			self.v1.set(text)
			Label(self.popUpWindow1,textvariable=self.v1,wraplength=350,font=('Helvetica',12)).grid()
		else:
			self.v1.set(text)
			

	def pprintPol(self,pointObject):
		from policyEvaluation import PolicyConstructor
		#opens new window, prints specifications of policy
		pC=PolicyConstructor(self.vocab,self.fields)
		policy=self.policyLookup[int(pointObject)]
		text=str(pC.pprintPolicyReturn(policy))  #will display specifications
		if self.popUpWindow2==None:
			self.popUpWindow2=Toplevel(self.master,bd=3,relief='flat')
			self.popUpWindow2.geometry('400x300+880-50') #see geometry strings in tkinter tutorial
			self.popUpWindow2.transient(self.canvas)
			self.v2=StringVar()
			self.v2.set(text)
			Label(self.popUpWindow2,textvariable=self.v2,wraplength=350,font=('Helvetica',12)).grid(sticky=N,row=0)  #note: was pack
			Button(self.popUpWindow2,text='zipcodes',command=lambda: self.createZipWd(policy)).grid(sticky=N,row=1)  #note: was pack	
			
			
		else:
			self.v2.set(text)
		


	def closeWindow(self,ptString,defaultFill=None,defaultOutline=None,closePops=True):
		#from close button bind
		self.canvas.delete('window1')
		self.canvas.itemconfig(ptString,fill=defaultFill,outline=defaultOutline) #returns red pt back to default
		self.prevPt=[] 
		if closePops: #pop up windows should be closed and reinit
			if not self.popUpWindow1==None: 
				self.popUpWindow1.withdraw()
			if not self.popUpWindow2==None:
				self.popUpWindow2.withdraw()
			self.popUpWindow1=None
			self.popUpWindow2=None #so it can be reinitialized
			self.TT=None
		 #deletes window displayed when point clicked, use window tag because
		#windows stack on top of each other, closeWindow closes all of them
		#note: function should be revised

	def createZipWd(self,policy,update=False):
		
	  from policyEvaluation import PolicyConstructor
          pC=PolicyConstructor(self.vocab,self.fields)
	  text=pC.pprintPolicyReturn(policy,printZip=True)
	  if self.TT==None:
		 #creates window to display zipcodes, just resizes second popup and adds scroll, toggle on and off
		self.popUpWindow2.geometry('400x500') 
		self.frame=Frame(self.popUpWindow2)
		self.TT=Text(self.frame,height=20,width=50,background='white')
		scroll=Scrollbar(self.frame)
		scroll.config(command=self.TT.yview)
		self.TT.configure(yscrollcommand=scroll.set)
		self.TT.grid(row=2,column=0) #pack side left
		self.TT.insert('end',text)
		scroll.grid(row=2,column=1,sticky=N+S)  #pack side right 
		self.frame.grid()
		#note: disallow text editing, note: toggle on off is .grid_remove and recreate. inconsistent

	  else:
		if not update:     #shouldnt be updated, should be closed   
			#if self.TT not None, zip button clicked with window already showing, so remove it
			self.frame.grid_remove()
			self.TT=None
			self.popUpWindow2.geometry('400x300') #reset size
			#self.TT.delete('1.0','end')
			#self.TT.insert('end','hello friend') #note: text instead
		else:
			self.TT.delete('1.0','end')
			self.TT.insert('end',text) #note: text instead
		
		

	def moveModel(self,event):	
		#allows model to be moved around canvas by arrow keys, scale can be changed as needed
		coords=self.canvas.coords(self.text[2])
		SCALE=30
		if event.keysym=='Up':
			self.centroid[1]-=SCALE
			self.canvas.coords(self.text[2],coords[0],coords[1]-SCALE)
		if event.keysym=='Down':
			self.centroid[1]+=SCALE
			self.canvas.coords(self.text[2],coords[0],coords[1]+SCALE)
		if event.keysym=='Right':
			self.centroid[0]+=SCALE
			self.canvas.coords(self.text[2],coords[0]+SCALE,coords[1])
		if event.keysym=='Left':
			self.centroid[0]-=SCALE
			self.canvas.coords(self.text[2],coords[0]-SCALE,coords[1])
		self.updateTasks()
		



	def incDistance(self,event):
		self.distance+=50
		self.updateTasks()


	def decDistance(self,event):
		self.distance-=50
		self.updateTasks()


	def initMouse(self,event): 
		#sets initial position for rotate()
		return [event.x,event.y]


	def rotate(self,event):
		#rotates camera based on mouse position, click before motion initializes self.xyMouse
		#next motion causes rotation
		if self.xyMouse[0] < event.x:
			self.rotation-=.05
		if self.xyMouse[0] > event.x:
			self.rotation+=.05
		if self.xyMouse[1] > event.y:
			self.elevation-=.05
		if self.xyMouse[1] < event.y:
			self.elevation+=.05
		self.updateTasks()
		self.xyMouse=[event.x,event.y]


	def rotateFine(self,event):
		if self.xyMouse[0] < event.x:
			self.rotation-=.005
		if self.xyMouse[0] > event.x:
			self.rotation+=.005
		if self.xyMouse[1] > event.y:
			self.elevation-=.005
		if self.xyMouse[1] < event.y:
			self.elevation+=.005
		self.updateTasks()
		self.xyMouse=[event.x,event.y]

	
	
	def updateTasks(self):
		"""redraws points,axes, and text when rotated, etc"""
		ptsTrans=[]
		####this section changes axes####
		for pt in self.axPts:
			newPt=self.devshedCalc(pt,self.focusPt)
			newPt[0]+=self.centroid[0]
			newPt[1]=self.centroid[1]-newPt[1]
			ptsTrans.append(newPt)
		for a in range(3):
			self.canvas.coords(self.axes[a],ptsTrans[0][0],ptsTrans[0][1],ptsTrans[a+1][0],ptsTrans[a+1][1])
		####this section rotates scatter points####
		index=0
		self.scatDepth=[] #reinitialize with devshedCalc, below
		RMAX=5 
		for pt in self.scatPts:
			newPt=self.devshedCalc(pt,self.focusPt)
			newPt[0]+=self.centroid[0]
			newPt[1]=self.centroid[1]-newPt[1]
			#self.scatDepth has been updated, use for resizing points
			R=float(self.scatDepth[index])/310*RMAX+1+RMAX
			self.canvas.coords(self.ptObjs[index],newPt[0]+R,newPt[1]+R,newPt[0]-R,newPt[1]-R)
			index+=1
		####this section cause text labels to follow####
		self.setText(self.axPts[1],self.axPts[2])
		"""self.canvas.coords(self.text[0],ptsTrans[1][0],ptsTrans[1][1])	
		self.canvas.coords(self.text[1],ptsTrans[2][0],ptsTrans[2][1])
		#self.canvas.coords(self.text[2],ptsTrans[3][0],ptsTrans[3][1]) # I don't like how the animation looks for 'ZIPCODE'"""
		self.updateOverlap()  #so points overlap corresponding to depth
		

	
	def projAttempt4(self):
		from math import pi
		"""focus point is direction camera points in, rotation spins about z axis (looks like righthand sys), 
		elevation spins about NEW camera x-axis, easiest to think about in that order.  
		focal point, distance is out of y. By default--all values zero--, y=out, x=right, z=up.
		depeding on focus point,  x axis rotation depends on camera, but z axis rotation is absolute"""
		#trying new way to project points
		#keep in mind y axis increases down screen, some math may be negative to counteract this
		self.axes=[] #keeps track of axis objects
		axPtsTrans=[] #2d points after projection, this is what actually goes on screen
		######CAMERA SETTINGS#############################
		self.focusPt=[0,0,0]
		self.elevation=30./180*pi 
		self.rotation=135./180*pi   
		self.distance=10
		##################################################
		#initialize axes, call setPts to init points, then init text
		self.scaleDist=300 #upper bound of point coord
		axPt0=[0,0,0] #origin
		axPt1=[300,0,0] #X axis
		axPt2=[0,300,0] #Y axis
		axPt3=[0,0,300] #Z axis
		self.axPts=[axPt0,axPt1,axPt2,axPt3]
		self.scatDepth=[] #will correspond with self.scatPts, keeps track of "out of screen" coord
		self.text=None #will be initialized by self.setText
		for pt in self.axPts:
			newPt=self.devshedCalc(pt,self.focusPt)
			newPt[0]+=self.centroid[0]
			newPt[1]=self.centroid[1]-newPt[1]
			axPtsTrans.append(newPt)
		for a in range(3):
			#keeps track of all projected points
			self.axes.append(self.canvas.create_line(axPtsTrans[0],axPtsTrans[a+1]))
		self.canvas.itemconfig(self.axes[0],width=1)
		self.canvas.itemconfig(self.axes[1],width=2)
		self.canvas.itemconfig(self.axes[2],dash=4)
		#self.canvas.itemconfig(self.axes[1],fill='red') #change back for axis color
		self.scatDepth=[] #reinitialize
		self.setPts() #plots scatter points	
		self.setText(axPt1,axPt2,axPtsTrans)  #sets the text at end of axes
				

	def devshedCalc(self,pt,focus):
		"""for projAttempt4(), default camera looks down y axis, transforms simple 3d points to 3d
		points relative to camera angle, then to points that can be displayed in 2d"""
		from math import cos,sin
		from numpy import matrix
		elevation=self.elevation
		rotation=self.rotation
		distance=self.distance
		pt=matrix(str(pt[0])+';'+str(pt[1])+';'+str(pt[2]))
		focus=matrix(str(focus[0])+';'+str(focus[1])+';'+str(focus[2]))
		a1=pt-focus
		#now the rotation matrices
		M1=matrix('1,0,0;'+'0,'+str(cos(-elevation))+','+str(sin(elevation))+';'+'0,'+str(-sin(-elevation))+','+str(cos(elevation))) 
		M2=matrix(str(cos(rotation))+','+str(sin(rotation))+',0;'+str(-sin(rotation))+','+str(cos(rotation))+',0;0,0,1')
		a2=M1*M2*a1
		a3=[a2[0,0],a2[1,0]-distance,a2[2,0]] #note: if realistic zoom needed, change +distance to -distance
		px=a3[0]*(1500-a3[1])/1500
		py=a3[2]*(1500-a3[1])/1500
		self.scatDepth.append(-a2[1,0]+distance) #for resizing points, only negative because screen y axis is inverted
		return [px,py]


	def setPts(self):
		from random import randint
		"""plots 3d scatter points"""
		self.scatPts=[]
		for policy in self.solPols:
			polPt=self.genPolicyPt(policy,self.vocab,self.fields)
			self.scatPts.append(polPt)
		"""#uncomment for random point demo#################
		for a in range(50):
			pt=[randint(0,300),randint(0,300),randint(0,300)]
			pt.append(randint(1,30)) #random overall rating
			if randint(0,5)==3:
				pt.append(False)
			else:
				pt.append(True)   
			self.scatPts.append(pt)
		#################################################"""
		RMAX=5 #limit for point size
		self.ptObjs=[]	
		self.policyLookup={} #will be dictionary connection point object to particular policy
		self.ratingLookup={} #will take a point object and output its overall rating
		index=0
		for pt in self.scatPts:	
			newPt=self.devshedCalc(pt, self.focusPt)
			newPt[0]+=self.centroid[0]
			newPt[1]=self.centroid[1]-newPt[1]
			C=newPt
			#self.scatDepth has been initialized, use for radius of points
			R=float(self.scatDepth[index])/310*RMAX+1+RMAX #changes point size to simulate depth
			fill='black'
			outline='white'
			#note: will later be changed to tell when male or female
			if not pt[4]:
				#5th value in pt is boolean for sex identified or not
				fill='white'
				outline='black'
			self.ptObjs.append(self.canvas.create_oval(C[0]+R,C[1]+R,C[0]-R,C[1]-R,fill=fill,width=1,activefill='red',activeoutline='black',outline=outline))
			self.policyLookup[self.ptObjs[index]]=self.solPols[index]
			self.ratingLookup[self.ptObjs[index]]=pt[3] #pt[3] was stored as overall rating
			index+=1
		self.prevPt=[] #used in displayPt method
		for obj in self.ptObjs:  #add clickableness to points
			self.canvas.tag_bind(obj,"<Button-1>",lambda event,arg=str(obj):self.displayPt(event,arg)) 
			#just like bind, but with lambda can pass additional argument: obj to keep track of which pt clicked
		self.updateOverlap()    #makes sure points overlap correctly corresponding to depth
		


	def setText(self,axPt1,axPt2,axPtsTrans=None):  #axPtsTrans just for z axis text
		#change axPts slightly to not clash with age and race text ie. x and y axis
		a1x=axPt1[0]+20
		a2y=axPt2[1]+20
		#note: consider revising axPt math
		a1=[a1x,axPt1[1],axPt1[2]]
		a2=[axPt2[0],a2y,axPt2[2]]
	        #note, could call PAD for settings
		#point that ended x axis now is longer, to be end of text 'AGE'
		textPt1=self.devshedCalc(a1,self.focusPt)
		textPt1[0]+=self.centroid[0]
		textPt1[1]=self.centroid[1]-textPt1[1]
		textPt2=self.devshedCalc(a2,self.focusPt)
		textPt2[0]+=self.centroid[0]
		textPt2[1]=self.centroid[1]-textPt2[1]
		if self.text is None:  #this means first initialization
			self.text=[self.canvas.create_text(textPt1,text='RACE')]
			self.text.append(self.canvas.create_text(textPt2,text='AGE'))
			self.text.append(self.canvas.create_text(axPtsTrans[3][0],axPtsTrans[3][1]-60,text='ZIPCODE'))
		else:   
			#this means text has already been displayed, and needs to be rotated, (setText called by rotate())
			self.canvas.coords(self.text[0],textPt1[0],textPt1[1])
			self.canvas.coords(self.text[1],textPt2[0],textPt2[1])


	def genPolicyPt(self,policy,vocab,fields):
		"""takes in a solution policy, outputs 3d coordinates of point in solution space """
		#the higher the scale, the less it affect position
		RACESCALE=100 #effectively max value
		AGESCALE=250  #ef max value
		ZIPSCALE=300  #ef max value
		assert fields[0].upper()=='RACE'
		assert fields[2].upper()=='AGE'
		assert fields[3].upper()=='ZIP5'
		ratings=fieldEval(policy,vocab,fields) #gives [race rating,sex rating,age rating, zip5 rating, overall]
		#note: make more compatible, with different ordered fields
		raceDist=float(ratings[0])/RACESCALE*self.scaleDist
		if raceDist>self.scaleDist:
			raceDist=self.scaleDist
		ageDist=float(ratings[2])/AGESCALE*self.scaleDist
		if ageDist>self.scaleDist:
			ageDist=self.scaleDist
		zipDist=float(ratings[3])/ZIPSCALE*self.scaleDist	
		if zipDist>self.scaleDist:
			zipDist=self.scaleDist
		newPt=[raceDist,ageDist,zipDist,raceDist+ageDist+zipDist] #keep track of point lcoation(x,y,z) and overall rating (w)
		if ratings[1]==100:  #note: changed because square
			newPt.append(True)  #sex is identified
		if ratings[1]==0:
			newPt.append(False)  #sex is not identified
		return newPt
	


	def D3projection(self):	
		#probably not going to be used!!						    
		##test 3d projection abilities                                      
		##note: change names			 			    
		"""cameraPt and cameraAng(euler angles) projects 3d points onto , z=out of page x=left y=down
		userPos takes x,y,z based on new coord system, decides how offset to display """
		pt1=[0,0,0]
		pt2=[300,0,0]
		pt3=[0,300,0]
		pt4=[0,0,300]
		self.cameraAng=[0,0,0]
		self.pts=[pt1,pt2,pt3,pt4]
		self.cameraPt=[5,.1,.1]
		self.userPos=[.1,-10,.1] #[1,.01,.01], [1,-100,.01] uP[0,1] offset view right,down, respectively
		self.axes=[]
		ptsTrans=[]
		##self.cameraAng # in radians
		for pt in self.pts:
			newPt=self.projectPt(pt,self.cameraPt,self.cameraAng,self.userPos)
			R=4
			ptsTrans.append(newPt)
			#self.canvas.create_oval(C[0]+R,C[1]+R,C[0]-R,C[1]-R,fill='black',width=1,activefill='white',activeoutline='white',outline='white')
		for a in range(3):
			self.axes.append(self.canvas.create_line(ptsTrans[0],ptsTrans[a+1]))
		self.canvas.itemconfig(self.axes[0],fill='blue')
		self.canvas.itemconfig(self.axes[1],fill='red')


	def projectPt(self,pt,cameraPt,cameraAng,userPos):
		#probably not going to be used!!
		"""takes a 3d point and converts to 2d"""
		from math import cos,sin
		from numpy import matrix
		M1=matrix('1,0,0'+';'+'0,'+str(cos(cameraAng[0]))+','+str(-sin(cameraAng[0]))+';'+'0,'+str(sin(cameraAng[0]))+','+str(cos(cameraAng[0])))
		M2=matrix(str(cos(cameraAng[1]))+',0,'+str(sin(cameraAng[1]))+';'+'0,1,0;'+str(-sin(cameraAng[1]))+',0,'+str(cos(cameraAng[1])))
		M3=matrix(str(cos(cameraAng[2]))+','+str(-sin(cameraAng[2]))+',0;'+str(sin(cameraAng[2]))+','+str(cos(cameraAng[2]))+',0;'+'0,0,1')
		Ma=matrix(str(pt[0])+';'+str(pt[1])+';'+str(pt[2]))
		Mc=matrix(str(cameraPt[0])+';'+str(cameraPt[1])+';'+str(cameraPt[2]))
		dXYZ=M1*M2*M3*(Ma-Mc)
		#dXYZ=self.projectionMath(pt,cameraPt,cameraAng) ##notationchange [1,0] to [1]
		bx=float(dXYZ[0,0]-userPos[0])*(float(userPos[2])/dXYZ[2,0])
		by=float(dXYZ[1,0]-userPos[1])*(float(userPos[2])/dXYZ[2,0])
		C=[self.centroid[0]+10,self.centroid[1]]
		C[0]+=bx
		C[1]+=by
		return C

		
	def projectionMath(self,pt,cameraPt,cameraAng):	
		#probably not going to be used!!
		from math import cos,sin
		from numpy import matrix
		dx=cos(cameraAng[1])*(sin(cameraAng[2])*(pt[1]-cameraPt[1])+cos(cameraAng[2])*(pt[0]-cameraPt[0]))-sin(cameraAng[1])*(pt[2]-cameraPt[2])
		dy=sin(cameraAng[0])*(cos(cameraAng[1])*(pt[2]-cameraPt[2])+sin(cameraAng[1])*(sin(cameraAng[2])*(pt[1]-cameraPt[1])+cos(cameraAng[2])*(pt[0]-cameraPt[0])))+cos(cameraAng[0])*(cos(cameraAng[2])*(pt[1]-cameraPt[1])-sin(cameraAng[2])*(pt[0]-cameraPt[0]))		
		dz=cos(cameraAng[0])*(cos(cameraAng[1])*(pt[2]-cameraPt[2])+sin(cameraAng[1])*(sin(cameraAng[2])*(pt[1]-cameraPt[1])+cos(cameraAng[2])*(pt[0]-cameraPt[0])))-sin(cameraAng[0])*(cos(cameraAng[2])*(pt[1]-cameraPt[1])-sin(cameraAng[2])*(pt[0]-cameraPt[0]))
		dvect=[dx,dy,dz]
		return dvect		

	
	def projMatrixMath(self,pt,cameraPt,cameraAng):
		##probably not going to be used!!
		from math import cos,sin
		from numpy import matrix
		M1=matrix('1,0,0'+';'+'0,'+str(cos(cameraAng[0]))+','+str(-sin(cameraAng[0]))+';'+'0,'+str(sin(cameraAng[0]))+','+str(cos(cameraAng[0])))
		M2=matrix(str(cos(cameraAng[1]))+',0,'+str(sin(cameraAng[1]))+';'+'0,1,0;'+str(-sin(cameraAng[1]))+',0,'+str(cos(cameraAng[1])))
		M3=matrix(str(cos(cameraAng[2]))+','+str(-sin(cameraAng[2]))+',0;'+str(sin(cameraAng[2]))+','+str(cos(cameraAng[2]))+',0;'+'0,0,1')
		Ma=matrix(str(pt[0])+';'+str(pt[1])+';'+str(pt[2]))
		Mc=matrix(str(cameraPt[0])+';'+str(cameraPt[1])+';'+str(cameraPt[2]))
		Md=M1*M2*M3*(Ma-Mc)
		return Md


##############################
#Below I define some helper functions for lines and geometry
##############################
	
	def distForm(self,point1,point2):
		"""Calculates distance formula given two points as two lists, [x,y]"""
		sq1=(point1[0]-point2[0])*(point1[0]-point2[0])
		sq2=(point1[1]-point2[1])*(point1[1]-point2[1])
		return sqrt(sq1+sq2)	

	def midPoint(self,point1,point2):
		"""Calculates midpoint given two points as two lists"""
		x=float(point1[0]+point2[0])/2
		y=float(point1[1]+point2[1])/2
		return [x,y]	

	
	

class Vector:
	"""very simply vector class with LIMITED capability"""

	def __init__(self,list):
		"""vector class to assist in linear calculations"""
		self.array=list
		self.size=len(list)

	def add(self,vect2):
		assert self.size==vect2.size
		for index in range(self.size):
			self.array[index]+=vect2.array[index]
		return self

	def sub(self,vect2):
		assert self.size==vect2.size
		for index in range(self.size):
			self.array[index]=self.array[index]-vect2.array[index]
		return self

	def mult(self,k):
		for index in range(self.size):
			self.array[index]*=k
		return self

	def getList(self):
		return self.array
		
##just in case::	
#C=[self.centroid[0],self.centroid[1]+zipDist]
##now move closer to other corners, proportional to rating
			##use vector equation c=a+k(b-a), where a and b are points on line, k is distance /
			##to travel to get to c
			###might not need vector stuff#####
			#Cvect=Vector(C)
			#Bvect=Vector(self.Coords[0:2])
			#operation to move point toward race corner
			#Cvect.add((Bvect.sub(Cvect)).mult(200./self.distForm(Cvect.getList(),self.Coords[0:2])))
			#Bvect=Vector(self.Coords[2:4])
			#Cvect.add((Bvect.sub(Cvect)).mult(200./self.distForm(Cvect.getList(),self.Coords[2:4]))) #ageDist
			
			##convert back to listC
			#C=Cvect.getList()


def extractPols(string):
	from policyEvaluation import PolicyString
	newStr=string.split(',')
	solPols=[]
	solPolStrings=[]
	for str in newStr:
		bits=[]
		for char in str:
			if char=='1':
				bits.append(True)
			if char=='0':
				bits.append(False)
		solPols.append(bits)
	for string in solPols:
		solPolStrings.append(PolicyString(string))
	return solPolStrings	
		
		
#851 lines






